/*
 * SPDX-License-Identifier: LGPL-2.1-or-later
 * Copyright Red Hat Inc. and Hibernate Authors
 */
package org.hibernate.orm.test.idgen.enhanced.auto;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import org.hamcrest.MatcherAssert;

import org.hibernate.annotations.CollectionId;
import org.hibernate.annotations.CollectionIdJdbcTypeCode;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.generator.Generator;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.IncrementGenerator;
import org.hibernate.id.enhanced.DatabaseStructure;
import org.hibernate.id.enhanced.SequenceStyleGenerator;
import org.hibernate.id.uuid.UuidGenerator;
import org.hibernate.mapping.IdentifierBag;
import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.orm.test.idgen.n_ative.GeneratorSettingsImpl;

import org.hibernate.testing.orm.junit.FailureExpectedExtension;
import org.hibernate.testing.util.ServiceRegistryUtil;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import java.sql.Types;
import java.util.Collection;
import java.util.UUID;

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.junit.Assert.assertThat;

/**
 * @author Steve Ebersole
 */
@ExtendWith( FailureExpectedExtension.class )
public class AutoGenerationTypeTests {
	@Test
	public void testAutoDefaults() {
		try (final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry()) {
			final Metadata metadata = new MetadataSources( ssr )
					.addAnnotatedClass( Entity1.class )
					.addAnnotatedClass( Entity2.class )
					.buildMetadata();

			final PersistentClass entityBinding = metadata.getEntityBinding( Entity1.class.getName() );
			final KeyValue idMapping = entityBinding.getRootClass().getIdentifier();
			Dialect dialect = new H2Dialect();
			final Generator generator = idMapping.createGenerator( dialect, entityBinding.getRootClass());
			final SequenceStyleGenerator entityIdGenerator = (SequenceStyleGenerator) (generator instanceof IdentifierGenerator ? (IdentifierGenerator) generator : null);

			final DatabaseStructure database1Structure = entityIdGenerator.getDatabaseStructure();

			// implicit name : `${entity-name}_seq`
			assertThat( database1Structure.getPhysicalName().render(), equalToIgnoringCase( "tbl_1_SEQ" ) );
			assertThat( database1Structure.getIncrementSize(), is( 50 ) );
		}
	}


	@Test
	public void testAutoGeneratedValueGenerator() {
		try (final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry()) {
			final Metadata metadata = new MetadataSources( ssr )
					.addAnnotatedClass( Entity1.class )
					.addAnnotatedClass( Entity2.class )
					.buildMetadata();

			final PersistentClass entityBinding = metadata.getEntityBinding( Entity2.class.getName() );
			final KeyValue idMapping = entityBinding.getRootClass().getIdentifier();
			Dialect dialect = new H2Dialect();
			final Generator generator = idMapping.createGenerator(
					dialect,
					entityBinding.getRootClass(),
					entityBinding.getIdentifierProperty(),
					new GeneratorSettingsImpl( metadata )
			);
			final SequenceStyleGenerator idGenerator = (SequenceStyleGenerator) (generator instanceof IdentifierGenerator ? (IdentifierGenerator) generator : null);

			final DatabaseStructure database2Structure = idGenerator.getDatabaseStructure();

			// GeneratedValue#generator value
			assertThat( database2Structure.getPhysicalName().render(), equalToIgnoringCase( "id_seq" ) );
			assertThat( database2Structure.getIncrementSize(), is( 50 ) );
		}
	}

	@Test
	public void testCollectionId() {
		try (final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry()) {
			final Metadata metadata = new MetadataSources( ssr )
					.addAnnotatedClass( Entity1.class )
					.addAnnotatedClass( Entity2.class )
					.buildMetadata();

			final PersistentClass entity1Binding = metadata.getEntityBinding( Entity1.class.getName() );

			// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			// Entity1#theTwos collection-id - implicit `${collection_table}_seq`

			final Property theTwos = entity1Binding.getProperty( "theTwos" );
			final IdentifierBag idBagMapping = (IdentifierBag) theTwos.getValue();
			final KeyValue collectionIdMapping = idBagMapping.getIdentifier();
			Dialect dialect = new H2Dialect();
			final Generator generator = collectionIdMapping.createGenerator( dialect, null);
			final SequenceStyleGenerator collectionIdGenerator = (SequenceStyleGenerator) (generator instanceof IdentifierGenerator ? (IdentifierGenerator) generator : null);

			final DatabaseStructure idBagIdGeneratorDbStructure = collectionIdGenerator.getDatabaseStructure();

			assertThat( idBagIdGeneratorDbStructure.getPhysicalName().render(), equalToIgnoringCase( "tbl_2_seq" ) );
			assertThat( idBagIdGeneratorDbStructure.getIncrementSize(), is( 50 ) );
		}
	}

	@Test
	public void testUuid() {
		try (final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry()) {
			final Metadata metadata = new MetadataSources( ssr )
					.addAnnotatedClass( Entity4.class )
					.buildMetadata();

			final PersistentClass entityBinding = metadata.getEntityBinding( Entity4.class.getName() );
			final Property identifierProperty = entityBinding.getRootClass().getIdentifierProperty();
			final KeyValue idMapping = entityBinding.getRootClass().getIdentifier();
			Dialect dialect = new H2Dialect();
			final Generator generator = idMapping.createGenerator( dialect, entityBinding.getRootClass(), identifierProperty, null );
			MatcherAssert.assertThat( generator, instanceOf( UuidGenerator.class ) );
		}
	}

	@Test
	public void testIncrement() {
		try (final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry()) {
			final Metadata metadata = new MetadataSources( ssr )
					.addAnnotatedClass( Entity3.class )
					.buildMetadata();

			final PersistentClass entityBinding = metadata.getEntityBinding( Entity3.class.getName() );
			final KeyValue idMapping = entityBinding.getRootClass().getIdentifier();
			Dialect dialect = new H2Dialect();
			final Generator generator = idMapping.createGenerator( dialect, entityBinding.getRootClass());
			final IdentifierGenerator idGenerator = generator instanceof IdentifierGenerator ? (IdentifierGenerator) generator : null;

			assertThat( idGenerator, instanceOf( IncrementGenerator.class ) );
		}
	}

	@Entity( name = "Entity1" )
	@Table( name = "tbl_1" )
	public static class Entity1 {
		@Id
		@GeneratedValue( strategy = GenerationType.AUTO )
		private Integer id;

		@OneToMany( mappedBy = "theOne" )
		@CollectionId( column = @Column( name = "child_" ), generator = "sequence" )
		@CollectionIdJdbcTypeCode( Types.INTEGER )
		private Collection<Entity2> theTwos;
	}

	@Entity( name = "Entity2" )
	@Table( name = "tbl_2" )
	public static class Entity2 {
		@Id
		@GeneratedValue( strategy = GenerationType.AUTO, generator = "id_seq" )
		private Integer id;

		@ManyToOne
		@JoinColumn
		private Entity1 theOne;
	}

	@Entity( name = "Entity3" )
	@Table( name = "tbl_3" )
	public static class Entity3 {
		@Id
		@GeneratedValue( generator = "increment" )
		private Integer id;
		private String name;
	}

	@Entity( name = "Entity4" )
	@Table( name = "tbl_4" )
	public static class Entity4 {
		@Id
		@GeneratedValue
		private UUID id;
		private String name;
	}
}
